﻿//
// (C) Copyright 2009 Irantha Suwandarathna (iranthas@hotmail.com)
// All rights reserved.
//

/* Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EffiProz.Core.Lib;
using EffiProz.Core.DataTypes;
using EffiProz.Core.Indexes;
using EffiProz.Core.Errors;
using EffiProz.Core.Persist;

namespace EffiProz.Core
{
    public class ExpressionLike : ExpressionLogical
    {

        private const int ESCAPE = 2;
        private const int TERNARY = 3;
        private Like likeObject;

        /**
         * Creates a LIKE expression
         */
        public ExpressionLike(Expression left, Expression right, Expression escape,
                        bool noOptimisation)
            : base(OpTypes.LIKE)
        {


            nodes = new Expression[TERNARY];
            nodes[LEFT] = left;
            nodes[RIGHT] = right;
            nodes[ESCAPE] = escape;
            likeObject = new Like();
            this.noOptimisation = noOptimisation;
        }

        private ExpressionLike(ExpressionLike other)
            : base(OpTypes.LIKE)
        {

            this.nodes = other.nodes;
            this.likeObject = other.likeObject;
        }

        public override void collectObjectNames(Set set)
        {
            base.collectObjectNames(set);
        }

        public override EfzList resolveColumnReferences(RangeVariable[] rangeVarArray,
                int rangeCount, EfzList unresolvedSet, bool acceptsSequences)
        {

            for (int i = 0; i < nodes.Length; i++)
            {
                if (nodes[i] != null)
                {
                    unresolvedSet = nodes[i].resolveColumnReferences(rangeVarArray,
                            rangeCount, unresolvedSet, acceptsSequences);
                }
            }

            return unresolvedSet;
        }

        public override Object getValue(Session session)
        {

            if (opType != OpTypes.LIKE)
            {
                return base.getValue(session);
            }

            Object leftValue = nodes[LEFT].getValue(session);
            Object rightValue = nodes[RIGHT].getValue(session);
            Object escapeValue = nodes[ESCAPE] == null ? null
                                                       : nodes[ESCAPE].getValue(
                                                           session);

            if (likeObject.isVariable)
            {
                lock (likeObject)
                {
                    likeObject.setPattern(session, rightValue, escapeValue,
                                          nodes[ESCAPE] != null);

                    return likeObject.compare(session, leftValue);
                }
            }

            return likeObject.compare(session, leftValue);
        }

        public override void resolveTypes(Session session, Expression parent)
        {

            for (int i = 0; i < nodes.Length; i++)
            {
                if (nodes[i] != null)
                {
                    nodes[i].resolveTypes(session, this);
                }
            }

            bool isEscapeFixedConstant = true;

            if (nodes[ESCAPE] != null)
            {
                if (nodes[ESCAPE]._isParam)
                {
                    throw Error.error(ErrorCode.X_42567);
                }

                nodes[ESCAPE].resolveTypes(session, this);

                isEscapeFixedConstant = nodes[ESCAPE].opType == OpTypes.VALUE;

                if (isEscapeFixedConstant)
                {
                    nodes[ESCAPE].setAsConstantValue(session);

                    if (nodes[ESCAPE].dataType == null)
                    {
                        throw Error.error(ErrorCode.X_42567);
                    }

                    if (nodes[ESCAPE].valueData != null)
                    {
                        long length;

                        switch (nodes[ESCAPE].dataType.typeCode)
                        {

                            case Types.SQL_CHAR:
                            case Types.SQL_VARCHAR:
                                length =
                                    ((String)nodes[ESCAPE].valueData).Length;
                                break;

                            case Types.SQL_BINARY:
                            case Types.SQL_VARBINARY:
                                length =
                                    ((BinaryData)nodes[ESCAPE].valueData).length(
                                        session);
                                break;

                            default:
                                throw Error.error(ErrorCode.X_42565);
                        }

                        if (length != 1)
                        {
                            throw Error.error(ErrorCode.X_22019);
                        }
                    }
                }
            }

            if (nodes[LEFT]._isParam)
            {
                nodes[LEFT].dataType = nodes[RIGHT].dataType;
            }
            else if (nodes[RIGHT]._isParam)
            {
                nodes[RIGHT].dataType = nodes[LEFT].dataType;
            }

            if (nodes[LEFT].dataType == null || nodes[RIGHT].dataType == null)
            {
                throw Error.error(ErrorCode.X_42567);
            }

            if (nodes[LEFT].dataType.isCharacterType()
                    && nodes[RIGHT].dataType.isCharacterType()
                    && (nodes[ESCAPE] == null
                        || nodes[ESCAPE].dataType.isCharacterType()))
            {
                bool ignoreCase =
                    nodes[LEFT].dataType.typeCode == Types.VARCHAR_IGNORECASE
                    || nodes[RIGHT].dataType.typeCode == Types.VARCHAR_IGNORECASE;

                likeObject.setIgnoreCase(ignoreCase);
            }
            else if (nodes[LEFT].dataType.isBinaryType()
                     && nodes[RIGHT].dataType.isBinaryType()
                     && (nodes[ESCAPE] == null
                         || nodes[ESCAPE].dataType.isBinaryType()))
            {
                likeObject.isBinary = true;
            }
            else
            {
                throw Error.error(ErrorCode.X_42565);
            }

            likeObject.dataType = nodes[LEFT].dataType;

            bool isRightArgFixedConstant = nodes[RIGHT].opType == OpTypes.VALUE;

            if (isRightArgFixedConstant && isEscapeFixedConstant
                    && nodes[LEFT].opType == OpTypes.VALUE)
            {
                setAsConstantValue(session);

                likeObject = null;

                return;
            }

            if (isRightArgFixedConstant && isEscapeFixedConstant)
            {
                likeObject.isVariable = false;
            }
            else
            {
                return;
            }

            Object pattern = isRightArgFixedConstant
                             ? nodes[RIGHT].getConstantValue(session)
                             : null;
            bool constantEscape = isEscapeFixedConstant
                                     && nodes[ESCAPE] != null;
            Object escape = constantEscape
                            ? nodes[ESCAPE].getConstantValue(session)
                            : null;

            likeObject.setPattern(session, pattern, escape, nodes[ESCAPE] != null);

            noOptimisation = true;
            return; //TODOx review Optimizations

            //if (noOptimisation)
            //{
            //    return;
            //}
           

            //if (likeObject.isEquivalentToUnknownPredicate())
            //{
            //    this.setAsConstantValue(null);

            //    likeObject = null;
            //}
            //else if (likeObject.isEquivalentToEqualsPredicate())
            //{
            //    opType = OpTypes.EQUAL;
            //    nodes[RIGHT] = new ExpressionValue(likeObject.getRangeLow(),
            //                                       SqlType.SQL_VARCHAR);
            //    likeObject = null;
            //}
            //else if (likeObject.isEquivalentToNotNullPredicate())
            //{
            //    Expression notNull = new ExpressionLogical(OpTypes.IS_NULL,
            //        nodes[LEFT]);

            //    opType = OpTypes.NOT;
            //    nodes = new Expression[UNARY];
            //    nodes[LEFT] = notNull;
            //    likeObject = null;
            //}
            //else
            //{
            //    if (nodes[LEFT].opType != OpTypes.COLUMN)
            //    {
            //        return;
            //    }

            //    if (!nodes[LEFT].dataType.isCharacterType())
            //    {
            //        return;
            //    }

            //    bool between = false;
            //    bool like = false;
            //    bool larger = false;

            //    if (likeObject.isEquivalentToBetweenPredicate())
            //    {

            //        // X LIKE 'abc%' <=> X >= 'abc' AND X <= 'abc' || max_collation_char
            //        larger = likeObject.hasCollation;
            //        between = !larger;
            //        like = larger;
            //    }
            //    else if (likeObject
            //          .isEquivalentToBetweenPredicateAugmentedWithLike())
            //    {

            //        // X LIKE 'abc%...' <=> X >= 'abc' AND X <= 'abc' || max_collation_char AND X LIKE 'abc%...'
            //        larger = likeObject.hasCollation;
            //        between = !larger;
            //        like = true;
            //    }

            //    if (!between && !larger)
            //    {
            //        return;
            //    }

            //    Expression leftBound =
            //        new ExpressionValue(likeObject.getRangeLow(),
            //                            SqlType.SQL_VARCHAR);
            //    Expression rightBound =
            //        new ExpressionValue(likeObject.getRangeHigh(session),
            //                            SqlType.SQL_VARCHAR);

            //    if (between && !like)
            //    {
            //        Expression leftOld = nodes[LEFT];

            //        nodes = new Expression[BINARY];
            //        nodes[LEFT] = new ExpressionLogical(OpTypes.GREATER_EQUAL,
            //                                            leftOld, leftBound);
            //        nodes[RIGHT] = new ExpressionLogical(OpTypes.SMALLER_EQUAL,
            //                                             leftOld, rightBound);
            //        opType = OpTypes.AND;
            //        likeObject = null;
            //    }
            //    else if (between && like)
            //    {
            //        Expression gte = new ExpressionLogical(OpTypes.GREATER_EQUAL,
            //                                               nodes[LEFT], leftBound);
            //        Expression lte = new ExpressionLogical(OpTypes.SMALLER_EQUAL,
            //                                               nodes[LEFT],
            //                                               rightBound);
            //        ExpressionLike newLike = new ExpressionLike(this);

            //        nodes = new Expression[BINARY];
            //        likeObject = null;
            //        nodes[LEFT] = new ExpressionLogical(OpTypes.AND, gte, lte);
            //        nodes[RIGHT] = newLike;
            //        opType = OpTypes.AND;
            //    }
            //    else if (larger)
            //    {
            //        Expression gte = new ExpressionLogical(OpTypes.GREATER_EQUAL,
            //                                               nodes[LEFT], leftBound);
            //        ExpressionLike newLike = new ExpressionLike(this);

            //        nodes = new Expression[BINARY];
            //        likeObject = null;
            //        nodes[LEFT] = gte;
            //        nodes[RIGHT] = newLike;
            //        opType = OpTypes.AND;
            //    }
            //}
        }

        public override String getSQL()
        {

            String left = getContextSQL(nodes[LEFT]);
            String right = getContextSQL(nodes[RIGHT]);
            StringBuilder sb = new StringBuilder();

            sb.Append(left).Append(' ').Append(Tokens.T_LIKE).Append(' ');
            sb.Append(right);

            /** @todo fredt - scripting of non-ascii escapes needs changes to general script logging */
            if (nodes[ESCAPE] != null)
            {
                sb.Append(' ').Append(Tokens.T_ESCAPE).Append(' ');
                sb.Append(nodes[ESCAPE].getSQL());
                sb.Append(' ');
            }

            return sb.ToString();
        }

        public override String describe(Session session, int blanks)
        {

            StringBuilder sb = new StringBuilder();

            sb.Append('\n');

            for (int i = 0; i < blanks; i++)
            {
                sb.Append(' ');
            }

            sb.Append("LIKE ");
            sb.Append(likeObject.describe(session));

            return sb.ToString();
        }
    }
}
